#!/usr/bin/env python
# -*- coding: utf-8; -*-

import time
from random import randrange

from django import forms
from django.core.validators import RegexValidator, ValidationError
from django.conf import settings
from django.core.mail import send_mail
from django_redis import get_redis_connection

from utils import encrypt
from web import models
from web.forms.bootstrap import BootstrapForm
"""
clean_email 对应两个按钮：
1. 注册或登录按钮，判断数据库(mysql 或 redis 中是否有该邮箱地址)，搭配 clean_code 检验验证码
2. 获取验证码按钮，额外注意发送是否失败，这时可能需要连接 redis
"""


class AuthEmailForm(forms.Form):
    tpl = forms.CharField(max_length=20)
    email = forms.EmailField()

    # def __init__(self, request, *args, **kwargs):
    #     super().__init__(*args, **kwars)
    #     self.request = request

    def clean_tpl(self):
        # 为什么 clean_tpl 需要下一行(否则出错)
        tpl = self.cleaned_data.get('tpl')
        if tpl not in settings.EMAIL_TEMPLATES.keys():
            raise ValidationError("邮件模板选择错误")
        return tpl

    def clean_email(self):
        """邮箱校验钩子，确认邮箱有接受邮件资格，发送邮件后，缓存验证码到redis中"""
        tpl = self.cleaned_data.get('tpl')
        email = self.cleaned_data.get('email')

        exists = models.UserInfo.objects.filter(email=email).exists()

        if tpl == 'login' and not exists:
            raise ValidationError('邮箱错误')
        elif tpl == 'code' and exists:  # 注册
            raise ValidationError("邮箱已被注册")

        # 发送邮件的部分，本可以写到 auth_code_email 视图函数中，但是放到视
        # 图函数后，若发送邮件失败，比如填入的邮箱地址符合邮箱格式但不存在，
        # 注册时上面的校验不会报错，但发件邮箱会报错发送失败，毕竟无法发给
        # 一个不存在的邮箱，这时我们就要在注册页面提示填入有效的邮箱地址。
        # 显然，在form中可以便于将错误返回给前端页面

        auth_code = randrange(1000, 9999)  # 需要定义在 html_message 前

        subject = 'minisaas 项目邮箱验证码'
        mail_content = settings.EMAIL_TEMPLATES[tpl]
        time_info = time.strftime("%m月%d日 %H:%M:%S", time.localtime())
        html_message = mail_content.format(auth_code, time_info)
        recipients = [
            email,
        ]
        sender = settings.EMAIL_HOST_USER

        # send_mail 函数返回值为 0 或 1, 对应成功发送信息的数量
        ret = send_mail(subject,
                        "",
                        sender,
                        recipients,
                        html_message=html_message)
        # {0: "邮件发送失败", 1: "邮件发送成功"}

        # 发送成功，将验证码写入 redis
        if ret:
            conn = get_redis_connection()
            conn.set(email, auth_code, ex=60)  # 键 -> 注册邮箱， 值 -> 验证码，有效期 60 秒
        else:
            raise ValidationError("请输入有效的邮箱地址")

        return email


class AuthSmsForm(BootstrapForm, forms.Form):
    pass


class RegisterForm(BootstrapForm, forms.ModelForm):
    class Meta:
        model = models.UserInfo
        # fields = "__all__"
        fields = [
            'name', 'password', 'confirm_password', 'phone', 'email', 'code',
        ]

    name = forms.CharField(label='用户名',
                               min_length=3,
                               max_length=32,
                               error_messages={
                                   'min_length': '用户名不可以少于3个字符',
                                   'max_length': '用户名不可大于32个字符',
                               },
                               validators=[
                                   RegexValidator(r'^[^0-9][0-9a-zA-Z_]+',
                                                  '用户名不可以数字开头，由数字字母下划线组成'),
                               ])
    phone = forms.CharField(label='手机号',
                            validators=[
                                RegexValidator(r'^(1[3-9])\d{9}$', '手机号格式错误'),
                            ])
    password = forms.CharField(label="密码",
                               min_length=8,
                               max_length=32,
                               error_messages={
                                   'min_length': '密码不可少于8个字符',
                                   'max_length': '密码不可大于32个字符'
                               },
                               widget=forms.PasswordInput())
    # attrs={'class':'form-control', 'placeholder': "请输入密码"}
    confirm_password = forms.CharField(label="重复密码",
                                       min_length=8,
                                       max_length=32,
                                       error_messages={
                                           'min_length': '密码不可少于8个字符',
                                           'max_length': '密码不可大于32个字符'
                                       },
                                       widget=forms.PasswordInput())
    code = forms.CharField(
        label='验证码'
    )  # widget=forms.TextInput(attrs={'class':'form-control', 'placeholder':"请输入验证码"}),

    def clean_name(self):
        name = self.cleaned_data['name']
        exists = models.UserInfo.objects.filter(name=name).exists()
        if exists:
            raise ValidationError('用户名已注册')
        return name

    def clean_phone(self):
        phone = self.cleaned_data['phone']
        exists = models.UserInfo.objects.filter(phone=phone).exists()
        if exists:
            raise ValidationError('手机号已注册')
        return phone

    # 返回加密密码
    def clean_password(self):
        password = self.cleaned_data['password']
        return encrypt.md5(password)

    def clean_confirm_password(self):
        # 注意上面的 clean_password 函数执行先于本函数(依 Meta.fields 列表内的顺序)
        # 所以执行本钩子函数时， cleaned_data 中的密码已经是加密后的密码
        pwd = self.cleaned_data['password']
        confirm = encrypt.md5(self.cleaned_data['confirm_password'])
        if pwd != confirm:
            raise ValidationError('两次输入密码不一致')
        return confirm

    def clean_email(self):
        email = self.cleaned_data['email']
        exists = models.UserInfo.objects.filter(email=email).exists()
        if exists:
            raise ValidationError('邮箱已注册')
        return email

    def clean_code(self):
        code = self.cleaned_data['code']
        email = self.cleaned_data['email']

        conn = get_redis_connection()
        redis_code = conn.get(email)  # 可能为 None，所以不可 conn.get(email).<other_attr>

        # 验证码是否存在
        if not redis_code:
            raise ValidationError('验证码失效或未发送，请重新点击发送邮箱验证码')

        # 验证码是否正确
        redis_code = redis_code.decode('utf-8')  # remember to decode the return bytes
        if code.strip() != redis_code:
            raise ValidationError('邮箱验证码错误，请重新输入')
        return code


class LoginEmailForm(BootstrapForm, forms.Form):
    email = forms.EmailField(label='邮箱')
    code = forms.CharField(label='验证码')

    def clean_email(self):
        email = self.cleaned_data.get('email')

        # exist = models.UserInfo.objects.filter(email=email).exists()
        # if not exist:
        #     raise ValidationError('邮箱不存在')
        # return email

        user_obj = models.UserInfo.objects.filter(email=email).first()
        if not user_obj:
            raise ValidationError('邮箱不存在')

        return user_obj  # 实际上 cleaned_data 中的 email 为用户对象(对应表中一行记录)

    def clean_code(self):
        code = self.cleaned_data.get('code')
        email = self.cleaned_data.get('email')

        if not email:
            return code

        conn = get_redis_connection()
        # NOTICE
        # 因为 elean_email 将 user_obj 保存在键 email 中，所以下面不可以直接 conn.get(email)
        redis_code = conn.get(email.email)  # 可能为 None，所以不可 conn.get(email).attr

        # 验证码是否存在
        if not redis_code:
            raise ValidationError('验证码失效或未发送，请重新点击发送邮箱验证码')

        # 验证码是否正确
        redis_code = redis_code.decode('utf-8')  # remember to decode the return bytes
        if code.strip() != redis_code:
            raise ValidationError('邮箱验证码错误，请重新输入')
        return code


class LoginSmsForm(BootstrapForm, forms.Form):
    phone = forms.CharField(label='手机号',
                            validators=[
                                RegexValidator(r'^(1[3-9])\d{9}$', '手机号格式错误'),
                            ])
    code = forms.CharField(label='验证码')


class LoginForm(BootstrapForm, forms.Form):

    # 重写 init 方法
    def __init__(self, session, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.session = session

    # 用户名或邮箱
    account = forms.CharField(max_length=32, label='用户名或邮箱')
    password = forms.CharField(label="密码",
                               min_length=8,
                               max_length=32,
                               error_messages={
                                   'min_length': '密码不可少于8个字符',
                                   'max_length': '密码不可大于32个字符'
                               },
                               widget=forms.PasswordInput(render_value=True))
    code = forms.CharField(label='图片验证码')
    """
    clean_account 流程可以省略，因为基本的 validator 验证已足够，剩余的流程见视图
    """

    # def clean_account(self):
    #     pass

    def clean_password(self):
        password = self.cleaned_data['password']
        return encrypt.md5(password)

    def clean_code(self):
        code = self.cleaned_data.get('code')
        # 从 session 获取验证码
        session_code = self.session.get('auth_code_image')
        if not session_code:
            raise ValidationError('验证码过期，请重新获取')

        if code.strip().upper() != session_code.upper():
            raise ValidationError('验证码输入错误')

        return code
